package com.derbysoft.lowcostadapter.schedule.parse.impl;

import com.derbysoft.lowcostadapter.commons.model.Hotel;
import com.derbysoft.lowcostadapter.commons.model.RoomStay;
import com.derbysoft.lowcostadapter.commons.model.Status;
import com.derbysoft.lowcostadapter.commons.redis.service.RoomStayCacheService;
import com.derbysoft.lowcostadapter.commons.repository.HotelRepository;
import com.derbysoft.lowcostadapter.commons.utils.DateTimeUtils;
import com.derbysoft.lowcostadapter.schedule.parse.ParseRoomStayService;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.LineIterator;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * Created by: jason
 * Date: 2012-09-05
 */
@Component("parseRoomStayService")
public class ParseRoomStayServiceImpl implements ParseRoomStayService {

    private Logger logger = LoggerFactory.getLogger(ParseRoomStayServiceImpl.class);

    private static final int BATCH_IMPORT_SIZE = 100000;
    private static final int PRINT_STEP = 500;

    private final AtomicBoolean updateRoomStay = new AtomicBoolean(false);

    @Autowired
    private HotelRepository hotelRepository;

    @Autowired
    private RoomStayCacheService roomStayCacheService;

    @Override
    public boolean importRoomStays(InputStream inputStream) {
        try {
            updateRoomStay.set(true);
            logger.info("importRoomStays begin");
            executeRoomStays(inputStream);
            logger.info("importRoomStays end");
            return true;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        } finally {
            updateRoomStay.set(false);
        }
        return false;
    }

    private void executeRoomStays(InputStream inputStream) throws IOException {
        Map<Integer, Hotel> hotelMap = hotelRepository.loadHotelMap(Status.ENABLED);

        List<RoomStay> roomStays = new ArrayList<RoomStay>();

        long totalBytes = inputStream.available();
        LineIterator lineIterator = IOUtils.lineIterator(inputStream, "UTF-16");
        long line = 0;

        while (lineIterator.hasNext()) {
            String restrictionInfo = lineIterator.nextLine();
            line++;

            String[] split = restrictionInfo.split(",");

            RoomStay roomStay = convertRoomStay(split, hotelMap);
            if (roomStay != null) {
                roomStays.add(roomStay);
                if (roomStays.size() > BATCH_IMPORT_SIZE) {
                    roomStayCacheService.saveAll(roomStays);
                    roomStays.clear();
                }
            }

            if (line % PRINT_STEP == 0) {
                printProcess("importRoomStays", totalBytes, inputStream, line);
            }
        }

        roomStayCacheService.saveAll(roomStays);
    }

    private RoomStay convertRoomStay(String[] infos, Map<Integer, Hotel> hotelMap) {
        try {
            if (infos.length < 2 || StringUtils.isBlank(infos[Field.PROPERTY_ID.ordinal()])) {
                return null;
            }
            Hotel hotel = hotelMap.get(getIntVal(infos, Field.PROPERTY_ID));
            if (hotel == null) {
                return null;
            }

            RoomStay roomStay = new RoomStay();
            roomStay.setPropertyID(String.valueOf(hotel.getPropertyID()));
            roomStay.setPropertyRoomTypeID(getIntVal(infos, Field.PROPERTY_ROOM_TYPE_ID));
            String startDate = getDateStringVal(infos, Field.START_DATE);
            if (startDate == null) {
                return null;
            }
            roomStay.setStart(startDate);
            String endDate = getDateStringVal(infos, Field.END_DATE);
            if (endDate == null) {
                return null;
            }
            roomStay.setEnd(endDate);
            roomStay.setMinStay(getIntVal(infos, Field.MIN_STAY));
            roomStay.setMaxStay(Math.max(getIntVal(infos, Field.MAX_STAY), 999));
            RoomStay.DayType dayType = RoomStay.DayType.fromString(getStringVal(infos, Field.DAY_TYPE));
            roomStay.setDayType(dayType != null ? dayType : RoomStay.DayType.ALL_DAYS);
            return roomStay;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    private void printProcess(String title, long totalBytes, InputStream inputStream, long line) throws IOException {
        long available = inputStream.available();
        long processed = totalBytes - available;
        double percent = 100.0 * processed / totalBytes;
        String message = String.format("%s Line:(%d) Processed:(%d/%d) Precent:(%.2f%%)",
                title, line, processed, totalBytes, percent);
        logger.info(message);
    }

    private String getStringVal(String[] infos, Field field) {
        return infos[field.ordinal()];
    }

    private int getIntVal(String[] infos, Field field) {
        return Integer.valueOf(getStringVal(infos, field));
    }

    private String getDateStringVal(String[] infos, Field field) {
        return DateTimeUtils.formatDate(DateTimeUtils.parse(getStringVal(infos, field)));
    }

    public void setHotelRepository(HotelRepository hotelRepository) {
        this.hotelRepository = hotelRepository;
    }

    public void setRoomStayCacheService(RoomStayCacheService roomStayCacheService) {
        this.roomStayCacheService = roomStayCacheService;
    }

    private enum Field {
        PROPERTY_ID,
        PROPERTY_NAME,
        PROPERTY_ROOM_TYPE_ID,
        START_DATE,
        END_DATE,
        MIN_STAY,
        MAX_STAY,
        DAY_TYPE
    }

    @Override
    public boolean isParsing() {
        return updateRoomStay.get();
    }
}
